package com.yy.base.servlet.upload;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpSession;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.IOUtils;
import com.yy.base.log.Logger;
import com.yy.base.log.LoggerFactory;

public class FileUploadService {
	private static final Logger logger = LoggerFactory.getLogger(FileUploadService.class);
	private long sizeLimited;
	private int sizeThreshold;
	private String storagePath;
	private String encoding;
	private String[] contentTypeAllowed;
	private ServletContext servletContext;
	private HttpServletRequest request;
	private ProgressListener listener;
	private HttpSession httpSession;
	private Map<String, Object> session;
	private Map<String, List<String>> fields;
	private List<File> files;
	private boolean wrapped;
	private boolean allowProxy;
	private String defaultStoragePath;
	private String relativePath;
	private boolean retainFileName;

	public FileUploadService() {
		fields = new HashMap<String, List<String>>();
		sizeThreshold = -1;
		sizeLimited = -1;
		allowProxy = false;
		retainFileName = false;
		defaultStoragePath = "/upload";
	}

	public List<File> getFilesInLocal(HttpServletRequest request) throws FileUploadException, Exception {
		return process(request, true);
	}

	public List<File> getFilesWithStream(HttpServletRequest request) throws Exception, FileUploadException {
		return process(request, false);
	}

	public List<InputStream> getInputStreams(HttpServletRequest request) throws Exception, FileUploadException, FileNotFoundException {
		List<InputStream> is = new ArrayList<InputStream>();
		InputStream s = null;
		for (File file : process(request, false)) {
			s = file.getInputStream();
			if (s != null) {
				is.add(file.getInputStream());
			}
		}
		return is;
	}

	/**
	 * 处理上传
	 */
	@SuppressWarnings("unchecked")
	private List<File> process(HttpServletRequest request, boolean writeToFile) throws Exception, FileUploadException {
		if (files != null) {
			return files;
		}
		// 初始化
		ServletFileUpload upload = init(request);
		files = new ArrayList<File>();
		List<FileItem> items = null;
		File file = null;
		try {
			items = upload.parseRequest(request);
		} catch (FileUploadException e) {
			logger.debug(e.getMessage(), e);
			throw e;
		}
		if (items.size() == 0) {
			if (wrapped && allowProxy) {
				// 已经被其他上传组件(struts)处理过
				// 目前只处理struts2的情形
				dealWithStruts2(writeToFile);
			} else if (wrapped) {
				logger.error("获取文件字节流失败！确认是否使用了struts的文件上传拦截器？请禁用struts文件上传拦截器或者使用普通的servlet处理文件上传。[allowProxy=false]。");
			}
			return files;
		}
		for (FileItem item : items) {
			if (item.isFormField()) {
				try {
					// 表单域
					setFieldParameter(item.getFieldName(), encoding == null ? item.getString() : item.getString(encoding));
				} catch (UnsupportedEncodingException e) {
					// 字符编码集转换错误，仍然使用默认的字符编码集转换
					setFieldParameter(item.getFieldName(), item.getString());
					logger.error(e.getMessage(), e);
				}
			} else {
				if (item.getName() == null || "".equals(item.getName().trim())) {
					// 不含文件内容
					continue;
				}
				String fileName = getFileName(item.getName());
				if (isAllowed(item)) {
					// 允许上传
					try {
						file = getNewFile(fileName);
					} catch (IOException ex) {
						logger.error("创建文件失败！", ex);
						throw new FileUploadException(ex.getMessage());
					}
					file.setAllowed(true);
					setFieldParameter(item.getFieldName(), file.getName());
					if (writeToFile) {
						try {
							// 写入服务器文件系统中
							item.write(file);
						} catch (Exception e) {
							// 写入失败，设置为丢失
							file.setMissing(true);
							logger.error(e.getMessage(), e);
						}
					} else {
						// 获取输入流
						try {
							file.setInputStream(item.getInputStream());
						} catch (IOException e) {
							// 获取输入流失败，设置为丢失。
							file.setMissing(true);
							logger.error(e.getMessage(), e);
						}
					}
				} else {
					// 不允许上传，记录文件信息
					file = new File("");
					file.setAllowed(false);
					file.setMissing(true);
					file.setLength(item.getSize());
					setFieldParameter(item.getFieldName(), item.getName());
				}
				file.setFileName(fileName);
				file.setContentType(item.getContentType());
				files.add(file);
			}
		}
		return files;
	}

	/**
	 * 处理struts的上传拦截
	 */
	private void dealWithStruts2(boolean writeToFile) {
		try {
			Class<?> wrapper = Class.forName("org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper");
			Method getFileParameterNames = wrapper.getDeclaredMethod("getFileParameterNames", new Class[] {});
			Method getContentTypes = wrapper.getDeclaredMethod("getContentTypes", new Class[] { String.class });
			Method getFiles = wrapper.getDeclaredMethod("getFiles", new Class[] { String.class });
			Method getFileNames = wrapper.getDeclaredMethod("getFileNames", new Class[] { String.class });
			@SuppressWarnings("unchecked")
			Enumeration<String> fieldNames = (Enumeration<String>) getFileParameterNames.invoke(request, new Object[] {});
			File file = null;
			BufferedInputStream in = null;
			BufferedOutputStream out = null;
			while (fieldNames != null && fieldNames.hasMoreElements()) {
				String fieldName = fieldNames.nextElement();
				String[] contentTypes = (String[]) getContentTypes.invoke(request, fieldName);
				if (contentTypes != null && contentTypes.length > 0) {
					String[] fileNames = (String[]) getFileNames.invoke(request, fieldName);
					if (fileNames != null && fileNames.length > 0) {
						java.io.File[] files = (java.io.File[]) getFiles.invoke(request, fieldName);
						if (files != null && files.length > 0) {
							for (int i = 0; i < files.length; i++) {
								String fileName = getFileName(fileNames[i]);
								if (isAllowed(files[i].length(), contentTypes[i])) {
									file = getNewFile(fileName);
									file.setAllowed(true);
									file.setMissing(false);
									if (writeToFile) {
										try {
											in = new BufferedInputStream(new FileInputStream(files[i]));
											out = new BufferedOutputStream(new FileOutputStream(file));
											IOUtils.copy(in, out);
										} catch (Exception e) {
											file.setMissing(true);
											logger.error(e.getMessage(), e);
										} finally {
											if (in != null) {
												try {
													in.close();
												} catch (IOException e) {
												}
											}
											if (out != null) {
												try {
													out.close();
												} catch (IOException e) {
												}
											}
										}
									} else {
										try {
											file.setInputStream(new FileInputStream(files[i]));
										} catch (IOException e) {
											file.setMissing(true);
											logger.error(e.getMessage(), e);
										}
									}
								} else {
									file = new File("");
									file.setAllowed(false);
									file.setMissing(true);
									file.setLength(files[i].length());
								}
								file.setContentType(contentTypes[i]);
								file.setFileName(fileName);
								this.files.add(file);
							}
						}
					}
				}
			}
		} catch (Exception e) {
			logger.error("获取struts2 MultiPartRequestWrapper 失败！请禁用struts文件上传拦截器或者使用普通的servlet处理文件上传。[allowProxy=true]");
			logger.error(e.getMessage(), e);
		}
	}

	private String getFileName(String filePath) {
		if (filePath == null) {
			return "";
		}
		filePath = filePath.replace('\\', '/');
		return filePath.substring(filePath.lastIndexOf('/') + 1);
	}
	public HttpServletRequest getRequest() {
		return wrapped ? request : new RequestParameterWrapper(request);
	}

	/**
	 * 判断是否允许上传
	 */
	private boolean isAllowed(FileItem item) {
		return isAllowed(item.getSize(), item.getContentType());
	}

	private boolean isAllowed(long length, String contentType) {
		boolean allowed = true;
		if (sizeLimited > -1) {
			allowed = length > sizeLimited ? false : true;
		}
		if (allowed && contentTypeAllowed != null) {
			allowed = contentType == null ? false : true;
			if (allowed) {
				for (String type : contentTypeAllowed) {
					if (contentType.equalsIgnoreCase(type.trim())) {
						return true;
					}
				}
				return false;
			}
		}
		return allowed;
	}

	/**
	 * 生成一个新的文件
	 */
	private File getNewFile(String fileName) throws Exception, IOException {
		String path = getFilePath(fileName);
		File file = new File(path);
		file.createNewFile();
		try {
			if (relativePath != null) {
				file.setRelativePath(relativePath);
				file.setUrl(new URL(getContextUrl() + relativePath));
				relativePath = null;
			}
		} catch (MalformedURLException e) {
			logger.debug(e.getMessage(), e);
		}
		return file;
	}

	/**
	 * 获取文件绝对路径
	 */
	private String getFilePath(String fileName) throws Exception {
		String path = getStoragePath();
		java.io.File storagePath = new java.io.File(path);
		if (!storagePath.exists()) {
			storagePath.mkdirs();
		}
		String name = null;
		if (retainFileName) {
			name = UUID.randomUUID().toString() + "_" + fileName;
		} else {
			name = UUID.randomUUID().toString();
		}
		relativePath = relativePath == null ? null : relativePath + name;
		return path + name;
	}

	/**
	 * 获取存储路径
	 */
	private String getStoragePath() throws Exception {
		String path = null;
		getServletContext();
		if (storagePath == null || "".equals(storagePath = storagePath.trim())) {
			if (servletContext != null) {
				// 使用默认的存储路径
				path = servletContext.getRealPath("") + (defaultStoragePath.startsWith("/") ? defaultStoragePath : "/" + defaultStoragePath);
				path = path.endsWith("/") ? path : path + "/";
			}
			// servletContext为null，则path为null。
		} else {
			path = checkRealPath(storagePath.replace("\\", "/"));
		}
		if (path != null) {
			Calendar c = Calendar.getInstance();
			// 以年、月、周来分目录
			String subPath = c.get(Calendar.YEAR) + "/" + (c.get(Calendar.MONTH) + 1) + "/" + c.get(Calendar.WEEK_OF_MONTH) + "/";
			relativePath += subPath;
			path += subPath;
		} else {
			throw new Exception("未设置存储路径。请设置物理路径(StoragePath)或者上下文对象(ServletContext)。");
		}
		return path;
	}

	private String checkRealPath(String path) {
		path = path.endsWith("/") ? path : path + "/";
		if (servletContext != null) {
			String realPath = servletContext.getRealPath("").replace("\\", "/");
			if (path.indexOf(realPath) != -1) {
				// 使用的绝对路径
				relativePath = path.substring(realPath.length());
			} else {
				// 使用的相对路径
				path = path.startsWith("/") ? path : "/" + path;
				relativePath = path;
				path = realPath + path;
			}
		}
		return path;
	}

	private void getServletContext() {
		if (servletContext != null) {
			return;
		}
		// 尝试从struts2获取servletContext
		Class<?> ctx = findStruts2Context();
		if (ctx != null) {
			try {
				Method getServletContext = ctx.getDeclaredMethod("getServletContext", new Class[] {});
				servletContext = (ServletContext) getServletContext.invoke(null, new Object[] {});
			} catch (Exception e) {
				logger.debug(e.getMessage(), e);
			}
		}
	}

	private Class<?> findStruts2Context() {
		try {
			return Class.forName("org.apache.struts2.ServletActionContext", false, ClassLoader.getSystemClassLoader());
		} catch (Exception e) {
			try {
				return Class.forName("org.apache.struts2.ServletActionContext", false, Thread.currentThread().getContextClassLoader());
			} catch (Exception ex) {
				logger.debug(ex.getMessage(), ex);
				return null;
			}
		}
	}

	/**
	 * 初始化
	 */
	private ServletFileUpload init(HttpServletRequest request) throws Exception {
		if (request != null) {
			this.request = request;
			// 判断是否已经被struts文件上传拦截器处理过
			if (request instanceof HttpServletRequestWrapper) {
				wrapped = true;
			}
			if (!ServletFileUpload.isMultipartContent(request)) {
				throw new Exception("非POST请求，或enctype非multipart/form-data类型。");
			}
			DiskFileItemFactory factory = new DiskFileItemFactory();
			factory.setRepository(new File(getSystemTmpdir()));
			if (servletContext != null) {
				//factory.setFileCleaningTracker(FileCleanerCleanup.getFileCleaningTracker(servletContext));
			}
			if (sizeThreshold > -1) {
				factory.setSizeThreshold(sizeThreshold);
			}
			ServletFileUpload upload = new ServletFileUpload(factory);
			if (sizeLimited > -1) {
				upload.setFileSizeMax(sizeLimited);
			}
			if (listener != null) {
				upload.setProgressListener(listener);
			} else {
				if (session != null) {
					upload.setProgressListener(new DefaultProgressListener());
				} else {
					if (httpSession == null) {
						httpSession = request.getSession();
					}
					upload.setProgressListener(new DefaultProgressListener());
				}
			}
			return upload;
		} else {
			throw new NullPointerException("HttpServletRequest对象不能为null。");
		}
	}

	/**
	 * 设置请求参数
	 */
	private void setFieldParameter(String name, String value) {
		if (fields.get(name) == null) {
			fields.put(name, new ArrayList<String>(1));
		}
		fields.get(name).add(value);
	}

	/**
	 * 获取上下文URL
	 */
	private String getContextUrl() {
		StringBuilder sb = new StringBuilder();
		sb.append(request.getScheme());
		sb.append("://");
		sb.append(request.getServerName());
		sb.append(":");
		sb.append(request.getServerPort());
		sb.append(request.getContextPath());
		return sb.toString();
	}

	private String getSystemTmpdir() {
		return System.getProperty("java.io.tmpdir");
	}
	/**
	 * 内容摘要:<br>
	 * 处理监听器默认实现类。<br>
	 * @author chengwei
	 * @version 1.3.0
	 * @date 2012 10:48:09 AM
	 * 
	 * @修改历史<br> 修改日期 修改人员 版本 修改内容<br>
	 * -----------------------------------------<br>
	 * 
	 * @版权 版权所有(C)2012<br>
	 * @公司 拓维信息系统股份有限公司<br>
	 */
	class DefaultProgressListener implements ProgressListener {
		public void update(long bytesRead, long contentLength, int items) {
//			if (httpSession != null) {
//				httpSession.setAttribute(FileUploadService.BYTES_READ, bytesRead);
//				httpSession.setAttribute(FileUploadService.CONTENT_LENGTH, contentLength);
//				httpSession.setAttribute(FileUploadService.ITEM, items);
//			} else if (session != null) {
//				session.put(FileUploadService.BYTES_READ, bytesRead);
//				session.put(FileUploadService.CONTENT_LENGTH, contentLength);
//				session.put(FileUploadService.ITEM, items);
//			}
		}
	}
	/**
	 * 内容摘要:<br>
	 * HttpServletRequest装饰类<br>
	 * @author chengwei
	 * @version 1.3.0
	 * @date 2012 12:02:23 PM
	 * 
	 * @修改历史<br> 修改日期 修改人员 版本 修改内容<br>
	 * -----------------------------------------<br>
	 * 
	 * @版权 版权所有(C)2012<br>
	 * @公司 拓维信息系统股份有限公司<br>
	 */
	class RequestParameterWrapper extends HttpServletRequestWrapper {
		public RequestParameterWrapper(HttpServletRequest request) {
			super(request);
		}

		@Override
		public String[] getParameterValues(String name) {
			if (fields != null) {
				List<String> values = fields.get(name);
				if (values != null) {
					return values.toArray(new String[values.size()]);
				}
			}
			return super.getParameterValues(name);
		}

		@Override
		public String getParameter(String name) {
			if (fields != null) {
				List<String> values = fields.get(name);
				if (values != null && values.size() > 0) {
					return values.get(0);
				}
			}
			return super.getParameter(name);
		}

		@Override
		@SuppressWarnings("unchecked")
		public Map getParameterMap() {
			if (fields != null) {
				Map<String, String[]> res = new HashMap<String, String[]>();
				List<String> values = null;
				for (String name : fields.keySet()) {
					values = fields.get(name);
					res.put(name, values.toArray(new String[values.size()]));
				}
				return res;
			}
			return super.getParameterMap();
		}

		@Override
		@SuppressWarnings("unchecked")
		public Enumeration getParameterNames() {
			if (fields != null) {
				return Collections.enumeration(fields.keySet());
			}
			return super.getParameterNames();
		}

		@Override
		public String getCharacterEncoding() {
			if (encoding != null) {
				return encoding;
			}
			return super.getCharacterEncoding();
		}
	}

	public void setMaxSize(long sizeLimited) {
		this.sizeLimited = sizeLimited;
	}

	public void setServletContext(ServletContext context) {
		this.servletContext = context;
	}

	public void setAllowedContentType(String[] contentTypeAllowed) {
		this.contentTypeAllowed = contentTypeAllowed;
	}

	public void setStoragePath(String path) {
		this.storagePath = path;
	}

	public void setProgressListener(ProgressListener listener) {
		this.listener = listener;
	}

	public void setHttpSession(HttpSession session) {
		this.httpSession = session;
	}

	public void setSession(Map<String, Object> session) {
		this.session = session;
	}

	public void setSizeThreshold(int sizeThreshold) {
		this.sizeThreshold = sizeThreshold;
	}

	public void setCharacterEncoding(String encoding) {
		this.encoding = encoding;
	}

	/**
	 * 设置允许代理struts2文件拦截
	 */
	public void setAllowStrutsAgent(boolean allowed) {
		this.allowProxy = allowed;
	}

	/**
	 * 设置默认的存储路径
	 */
	public void setDefaultStoragePath(String path) {
		if (path != null && !"".equals(path = path.trim())) {
			this.defaultStoragePath = path.replace("\\", "/");
		}
	}

	/**
	 * 设置保存文件时，保留文件名
	 */
	public void setRetainFileName(boolean retainFileName) {
		this.retainFileName = retainFileName;
	}
}